#include "StdAfx.h"
#include "PictureSaver.h"

#ifdef _DEBUG
#include "crtdbg.h"
#endif

EImageFormat PictureSaver::GetImageFormat(const TCHAR* sFile)
{
  EImageFormat fmt = ifNone;

  if (!sFile)
    return fmt;

  static const TCHAR* imgTypes[] = { TEXT(".png"), TEXT(".jpg"), TEXT(".jpeg"), TEXT(".gif"), TEXT(".bmp") };
  static EImageFormat imgFormats[] = { ifPng, ifJpeg, ifJpeg, ifGif, ifBmp};
  static const int imgTypesCount = 5;

  TCHAR* ext = ::_tcsrchr(sFile, TEXT('.'));
  if (!ext)
    return fmt;

  for (int i = 0; i < imgTypesCount; i++)
  {
    if (::_tcsicmp(ext, imgTypes[i]) == 0)
    {
      fmt = imgFormats[i];
      break;
    }
  }

  return fmt;
}

const TCHAR* PictureSaver::GetFormatExt(EImageFormat format)
{
  switch(format)
  {
  case ifPng:
    return TEXT(".png");
  case ifGif:
    return TEXT(".gif");
  case ifJpeg:
    return TEXT(".jpg");
  case ifBmp:
    return TEXT(".bmp");
  default:
    return TEXT("");
  }
}

bool PictureSaver::LoadImageFromFile(IImage** pImage, const TCHAR* sFile)
{
  if (!pImage || !sFile)
    return false;

  HRESULT hr;
  IImagingFactory* pFactory = NULL;

  hr = ::CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&pFactory);
  if (FAILED(hr))
    return false;

  hr = pFactory->CreateImageFromFile(sFile, pImage);
  pFactory->Release();

  return SUCCEEDED(hr);
}

bool PictureSaver::SaveImageToFile(IImage* pImage, const TCHAR* sFile, EImageFormat format)
{
  if (!pImage || !sFile)
    return false;

  HRESULT hr;
  IImagingFactory* pFactory = NULL;

  hr = ::CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&pFactory);
  if (FAILED(hr))
    return false;

  CLSID encoderClassId;
  if (!GetEncoder(encoderClassId, format, pFactory))
  {
    pFactory->Release();
    return false;
  }

  IImageEncoder* imageEncoder = NULL;
  hr = pFactory->CreateImageEncoderToFile(&encoderClassId, sFile, &imageEncoder);
	if (FAILED(hr))
  {
    pFactory->Release();
    return false;
  }
	/*
		Call IImageEncoder::GetEncodeSink to retrieve an IImageSink interface. The IImageSink interface is implemented as follows: 
			a. Call IImageSink::BeginSink to negotiate the values contained in the ImageInfo structure for encoding the 
			   current frame. 
			b. Call IImageSink::SetPalette to pass color palette information about the current image frame to the image sink. 
			c. If you need to pass property data to the image sink, you can optionally call IImageSink::GetPropertyBuffer to 
			   obtain a buffer that will contain the property data. 
			d. If GetPropertyBuffer is called above, you must next call IImageSink::PushPropertyItems to transfer the 
			   property data to the image sink. 
			   The buffer that was allocated by GetPropertyBuffer must be deallocated in the implementation for 
			   PushPropertyItems. 
			e. Call IImageSink::PushPixelData or IImageSink::GetPixelDataBuffer to begin the data transfer, depending on how the 
			   image data is stored in the source: 
			   If the image source has allocated memory for the image, use PushPixelData. 
			   If the image source has not allocated memory for the image, use GetPixelDataBuffer. For each call to GetPixelDataBuffer, 
			   IImageSink::ReleasePixelDataBuffer must also be called. 
			f. Call ImageSink::EndSink to complete the encoding process. 
			g. Call ImageSink::Release to free the IImagesink interface. 				
	*/	

  IImageSink* imageSink = NULL;
  hr = imageEncoder->GetEncodeSink(&imageSink);
  if (FAILED(hr))
  {
    imageEncoder->Release();
    pFactory->Release();
	  return false;
  }

  hr = pImage->PushIntoSink(imageSink);

  if (SUCCEEDED(hr))
  {
    imageSink->EndSink(S_OK);
  }
  else
  {
    imageSink->EndSink(E_FAIL);
  }

  imageSink->Release();

  imageEncoder->TerminateEncoder();

  imageEncoder->Release();
  pFactory->Release();

  return SUCCEEDED(hr);
}

bool PictureSaver::SaveImageToStream(IImage* pImage, IStream* pStream, EImageFormat format)
{
  if (!pImage || !pStream)
    return false;

  HRESULT hr;
  IImagingFactory* pFactory = NULL;

  hr = ::CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&pFactory);
  if (FAILED(hr))
    return false;

  CLSID encoderClassId;
  if (!GetEncoder(encoderClassId, format, pFactory))
  {
    pFactory->Release();
    return false;
  }

  IImageEncoder* imageEncoder = NULL;
  hr = pFactory->CreateImageEncoderToStream(&encoderClassId, pStream, &imageEncoder);
	if (FAILED(hr))
  {
    pFactory->Release();
    return false;
  }
	/*
		Call IImageEncoder::GetEncodeSink to retrieve an IImageSink interface. The IImageSink interface is implemented as follows: 
			a. Call IImageSink::BeginSink to negotiate the values contained in the ImageInfo structure for encoding the 
			   current frame. 
			b. Call IImageSink::SetPalette to pass color palette information about the current image frame to the image sink. 
			c. If you need to pass property data to the image sink, you can optionally call IImageSink::GetPropertyBuffer to 
			   obtain a buffer that will contain the property data. 
			d. If GetPropertyBuffer is called above, you must next call IImageSink::PushPropertyItems to transfer the 
			   property data to the image sink. 
			   The buffer that was allocated by GetPropertyBuffer must be deallocated in the implementation for 
			   PushPropertyItems. 
			e. Call IImageSink::PushPixelData or IImageSink::GetPixelDataBuffer to begin the data transfer, depending on how the 
			   image data is stored in the source: 
			   If the image source has allocated memory for the image, use PushPixelData. 
			   If the image source has not allocated memory for the image, use GetPixelDataBuffer. For each call to GetPixelDataBuffer, 
			   IImageSink::ReleasePixelDataBuffer must also be called. 
			f. Call ImageSink::EndSink to complete the encoding process. 
			g. Call ImageSink::Release to free the IImagesink interface. 				
	*/	

  IImageSink* imageSink = NULL;
  hr = imageEncoder->GetEncodeSink(&imageSink);
  if (FAILED(hr))
  {
    imageEncoder->Release();
    pFactory->Release();
	  return false;
  }

  hr = pImage->PushIntoSink(imageSink);

  if (SUCCEEDED(hr))
  {
    imageSink->EndSink(S_OK);
  }
  else
  {
    imageSink->EndSink(E_FAIL);
  }

  imageSink->Release();

  imageEncoder->TerminateEncoder();

  imageEncoder->Release();
  pFactory->Release();

  return SUCCEEDED(hr);
}

bool PictureSaver::ResizeImage(IImage* pSrcImage, IImage** pDstImage, int& width, int& height, bool fitIfSmaller)
{
  if (!pSrcImage || !pDstImage || width == 0 || height == 0)
    return false;

  HRESULT hr;
  ImageInfo ii;
  hr = pSrcImage->GetImageInfo(&ii);
  if (FAILED(hr) || ii.Width == 0 || ii.Height == 0)
    return false;

  if (width >= (int)ii.Width && height >= (int)ii.Height && !fitIfSmaller)
  {
    width = ii.Width;
    height = ii.Height;
    *pDstImage = pSrcImage;
    return true;
  }

  UINT tw = ii.Width, th = ii.Height;
  if (!ScaleProportional(width, height, tw, th))
    return false;

  hr = pSrcImage->GetThumbnail(tw, th, pDstImage);
  if (SUCCEEDED(hr))
  {
    width = tw;
    height = th;
    pSrcImage->Release();
  }

  return SUCCEEDED(hr);
}

bool PictureSaver::GetEncoder(CLSID& id, EImageFormat format, IImagingFactory* pFactory)
{
  if (!pFactory)
    return false;

  HRESULT hr;
  UINT count;
	ImageCodecInfo* imageCodecInfo = NULL;
  hr = pFactory->GetInstalledEncoders(&count, &imageCodecInfo);
	if (FAILED(hr) || count == 0)
  {
    return false;
  }

	// Get the particular encoder to use
  const TCHAR* formatString;
	switch(format)
  {
  case ifPng:
    formatString = TEXT("image/png");
    break;
  case ifGif:
    formatString = TEXT("image/gif");
    break;
  case ifBmp:
    formatString = TEXT("image/bmp");
    break;
  case ifJpeg:
  default:
    formatString = TEXT("image/jpeg");
    break;
  }

  bool  found = false;

  for(UINT i = 0; i < count; i++)
  {
    if (::_tcscmp(imageCodecInfo[i].MimeType, formatString) == 0)
    {
   	  id = imageCodecInfo[i].Clsid;
      found = true;
   		break;
		}
  }
  CoTaskMemFree(imageCodecInfo);

  return found;
}

// **************************************************************************
// Function Name: ScaleProportional
// 
// Purpose: Scale the width and height to fit the given width and height
//          but maintain the proportion
//
// Arguments:
//    IN     UINT  uFitToWidth     - width of source image
//    IN     UINT  uFitToHeight    - height of source image
//    IN/OUT UINT* puWidthToScale  - width of image to scale to
//    IN/OUT UINT* puHeightToScale - height of image to scale to
//
// Return Values:
//    HRESULT - S_OK if success, failure code if not
//
bool PictureSaver::ScaleProportional(UINT uFitToWidth, UINT uFitToHeight, UINT &uWidthToScale, UINT &uHeightToScale)
{
    // Scale (*puWidthToScale, *puHeightToScale) to fit within (uFitToWidth, uFitToHeight), while
    // maintaining the aspect ratio
    int nScaledWidth = MulDiv(uWidthToScale, uFitToHeight, uHeightToScale);

    // If we didn't overflow and the scaled width does not exceed bounds
    if (nScaledWidth >= 0 && nScaledWidth <= (int)uFitToWidth)
    {
        uWidthToScale  = nScaledWidth;
        uHeightToScale = uFitToHeight;
    }
    else
    {
        uHeightToScale = MulDiv(uHeightToScale, uFitToWidth, uWidthToScale);
        
        // The height *must* be within the bounds [0, uFitToHeight] since we overflowed
        // while fitting to height
        if (uHeightToScale < 0 || uHeightToScale > uFitToHeight)
           return false;

        uWidthToScale  = uFitToWidth;
    }

    return true;
}
